package cn.charmaine.basic.base.service.impl;

import cn.charmaine.basic.Exception.CustomException;
import cn.charmaine.basic.base.service.ILoginService;
import cn.charmaine.basic.jwt.JwtUtils;
import cn.charmaine.basic.jwt.RsaUtils;
import cn.charmaine.basic.jwt.UserInfo;
import cn.charmaine.basic.util.*;
import cn.charmaine.org.domain.Logininfo;
import cn.charmaine.org.domain.User;
import cn.charmaine.org.domain.Wxuser;
import cn.charmaine.org.domain.dto.LoginDto;
import cn.charmaine.org.domain.dto.SmsCodeParam;
import cn.charmaine.org.domain.dto.WeChatDto;
import cn.charmaine.org.mapper.LogininfoMapper;
import cn.charmaine.org.mapper.UserMapper;
import cn.charmaine.org.mapper.WxuserMapper;
import cn.charmaine.system.domain.Menu;
import cn.charmaine.system.mapper.MenuMapper;
import cn.charmaine.system.mapper.PermissionMapper;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class LoginServiceImpl implements ILoginService {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private LogininfoMapper logininfoMapper;

    @Autowired
    private WxuserMapper wxuserMapper;

    @Autowired
    private JavaMailSender javaMailSender;//发送邮件

    @Autowired
    private PermissionMapper permissionMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Value("${jwt.rsa.pri}")// SPEL表达式读取yml配置文件中的值
    private String jwtRsaPrivate;

    /**
     * 图片验证码
     *
     * @param key
     * @return
     */
    @Override
    public String generateImage(String key) {
        //空值校验
        if (StringUtils.isEmpty(key)) {
            throw new CustomException("参数异常");
        }
        //生成随机一个长度为4的字符
        String randomString = StrUtils.getComplexRandomString(4);
        //redis保存生成随机字符
        redisTemplate.opsForValue().set(key, randomString, 5, TimeUnit.MINUTES);
        //返回生成验证码图片,base64加密后的字符串地址
        return VerifyCodeUtils.VerifyCode(113, 50, randomString);
    }

    /**
     * 短信验证 或 邮箱验证 注册
     *
     * @param param
     */
    @Override
    @Transactional
    public void sendSmsCode(SmsCodeParam param) {
        //用一个变量来接收前端传的手机号或者邮箱
        String phoneOrEmail = param.getPhone();
        if (StringUtils.isEmpty(phoneOrEmail)) {
            phoneOrEmail = param.getEmail();
        }
        //1.参数非空校验
        if (StringUtils.isEmpty(param.getImageCode())
                || StringUtils.isEmpty(param.getImageCodeKey())
                || StringUtils.isEmpty(phoneOrEmail)
        ) {
            throw new CustomException("参数异常");
        }
        User user = userMapper.selectByUsername(phoneOrEmail);
        //2.用户已经存在
        if (user != null) {
            throw new CustomException("用户已注册");
        }
        //用户不存在，允许注册
        //3.通过图片验证码key从redis中获取数据(imageValue)
        Object imageValue = redisTemplate.opsForValue().get(param.getImageCodeKey());
        if (imageValue == null) {
            throw new CustomException("图形验证码已过期");
        }
        //4.判断前端传递的图形验证码和redis中获取的是否一致（不区分大小写）
        String imageCode = param.getImageCode();
        if (!imageValue.toString().equalsIgnoreCase(imageCode)) {
            throw new CustomException("验证码输入不正确");
        }
        //5.拼接短信验证码smsCodeKey（register:手机号），从Redis中获取数据
        //String smsCodeKey = "register:" + param.getPhone();
        String smsCodeKey = "register:" + phoneOrEmail;
        Object smsCodeValue = redisTemplate.opsForValue().get(smsCodeKey);
        //Redis中获取到smsValue数据，说明不是第一次发送
        if (smsCodeValue != null) {
            String[] split = smsCodeValue.toString().split(":");
            //判断间隔时间是否超过一分钟
            //没有超过一分钟
            if (System.currentTimeMillis() - Long.valueOf(split[1]) < 1 * 60 * 1000) {
                throw new CustomException("请不要重复发送");
            } else {//没有失效，超过一分钟，再次发送同一个验证码
                //调用方法发送邮件或者发送短信验证码
                sendEmail(param, split[0]);
            }
        } else {//Redis中没有获取到smsValue数据，说明是第一次发送
            String code = StrUtils.getComplexRandomString(4);
            sendEmail(param, code);//调用方法发送邮件或者发送短信验证码
            redisTemplate.opsForValue().set(smsCodeKey, code + ":" + System.currentTimeMillis(), 3, TimeUnit.MINUTES);
        }

    }

    public void sendEmail(SmsCodeParam param, String code) {
        if (StringUtils.isEmpty(param.getPhone())) {
            //发送邮件
            SimpleMailMessage smm = new SimpleMailMessage();//创建邮件对象
            smm.setFrom("1798620237@qq.com");//发送人
            smm.setTo(param.getEmail());//收件人
            smm.setSubject("验证码");//邮件主题
            smm.setText(code);//邮件内容
            javaMailSender.send(smm);//发送邮件
        } else {
            System.out.println("手机验证码：》》》》》" + code);
        }

    }

    /**
     * 登录
     *
     * @param dto
     * @return
     */
    @Override
    public Map<String, Object> login(LoginDto dto) {
        //空参数校验
        if (StringUtils.isEmpty(dto.getAccount()) ||
                StringUtils.isEmpty(dto.getCheckPass()) ||
                StringUtils.isEmpty(dto.getType())
        ) {
            throw new CustomException("参数异常！");
        }
        //查询logininfo表
        Logininfo logininfo = logininfoMapper.selectByAccountAndType(dto.getAccount(), dto.getType());
        if (logininfo == null) {
            throw new CustomException("用户不存在！");
        }
        //如果有logininfo，判断密码是否正确
        /*
        String md5Password = MD5Utils.encrypByMd5(dto.getCheckPass() + logininfo.getSalt());
        if (!md5Password.equals(logininfo.getPassword())) {
            throw new CustomException("用户名或者密码错误！");
        }
        //如果密码正确，存redis，返回token，用户信息
        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);

        //返回给前端的信息里不能有salt与password
        logininfo.setSalt(null);
        logininfo.setPassword(null);
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);//设置token，看用户是否已经登录
        map.put("logininfo", logininfo);
        return map;
        */
        //用jwt替换上边无状态token
        return loginSuccessHandler(logininfo);
    }

    /**
     * jwt方法加密
     * @param logininfo
     * @return
     */
    private Map<String,Object> loginSuccessHandler(Logininfo logininfo){
        UserInfo userInfo = new UserInfo();
        userInfo.setLogininfo(logininfo);//设置个人信息
        Map<String, Object> map = new HashMap<>();
        //判断当前用户是主站还是后端管理系统
        if (logininfo.getType() == 1) {//主站  返回个人信息+token
            //获取私钥
            try {
                PrivateKey privateKey = RsaUtils
                        .getPrivateKey(FileCopyUtils
                                .copyToByteArray(this.getClass().getClassLoader().getResourceAsStream(jwtRsaPrivate)));
                //加密得到token（有过期时间）
                String token = JwtUtils.generateTokenExpireInMinutes(userInfo, privateKey, 60*24);
                logininfo.setSalt(null);
                logininfo.setPassword(null);
                map.put("token", token);
                //单纯返回个人信息
                map.put("logininfo", logininfo);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {//后端管理系统
            //如果是后端，返回token+个人信息+权限信息
            List<String> sns = permissionMapper.selectSnByLogininfoId(logininfo.getId());
            List<Menu> menus = menuMapper.selectMenuByLogininfoId(logininfo.getId());
            userInfo.setPermissions(sns);//设置权限信息（请求权限）
            userInfo.setMenus(menus);//设置权限信息（菜单权限）
            try {
                //获取私钥
                PrivateKey privateKey = RsaUtils
                        .getPrivateKey(FileCopyUtils
                        .copyToByteArray(this.getClass().getClassLoader().getResourceAsStream(jwtRsaPrivate)));
                //加密得到token
                String token = JwtUtils.generateTokenExpireInMinutes(userInfo, privateKey, 60*24);
                logininfo.setSalt(null);
                logininfo.setPassword(null);
                map.put("token", token);
                map.put("logininfo", logininfo);//个人信息
                map.put("permissions", sns);//权限信息（请求权限）
                map.put("menus", menus);//权限信息（菜单权限）
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return map;
    }

    /**
     * 微信扫码登录
     *
     * @param map
     * @return
     */
    @Override
    public AjaxResult wechat(Map<String, String> map) {
        //获取前端传来回调页面的code
        String code = map.get("code");
        if (StringUtils.isEmpty(code)) {
            throw new CustomException("微信的锅");
        }
        //通过code 请求微信服务，获取accessToken和openId
        String ackUrl = WxConstants.GET_ACK_URL;
        String url = ackUrl.replace("APPID", WxConstants.APPID)
                .replace("SECRET", WxConstants.SECRET)
                .replace("CODE", code);
        //使用httpclient组件发送http请求(请求微信服务)
        String s = HttpClientUtils.httpGet(url);
        //用alibaba的json解析工具
        JSONObject resultJson = JSONObject.parseObject(s);
        String access_token = resultJson.getString("access_token");
        String openid = resultJson.getString("openid");
        //通过openid查询用户是否已经注册
        Wxuser wxuser = wxuserMapper.selectByOpenid(openid);
        System.out.println(wxuser);
        //没有注册：返回acessToken，openid，给前端
        if (wxuser == null) {
            String bindUrl = "?access_token=" + access_token + "&openid=" + openid;
            return new AjaxResult().thisSuccess().setSuccess(false).setResultObj(bindUrl);
        }
        //注册过的，获取登录相关信息，生成token存入redis，返回前端实现免密登录
        Logininfo logininfo = logininfoMapper.selectByUserId(wxuser.getUserId());
        /*String token = UUID.randomUUID().toString();
        //存redis
        redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);
        //返回前端的数据
        Map<String, Object> resultMap = new HashMap<>();
        logininfo.setSalt(null);
        logininfo.setPassword(null);
        resultMap.put("token", token);
        resultMap.put("logininfo", logininfo);
        return new AjaxResult().thisSuccess().setResultObj(resultMap);*/
        //用jwt替换上边无状态token
        return new AjaxResult().thisSuccess().setResultObj(loginSuccessHandler(logininfo));
    }

    /**
     * 微信未注册过该app，绑定
     * @param weChatDto
     * @return
     */
    @Override
    public AjaxResult binder(WeChatDto weChatDto) {
        //空值校验
        if (StringUtils.isEmpty(weChatDto.getPhone()) ||
                StringUtils.isEmpty(weChatDto.getPassword()) ||
                StringUtils.isEmpty(weChatDto.getAccessToken()) ||
                StringUtils.isEmpty(weChatDto.getOpenId())
        ) {
            throw new CustomException("参数异常");
        }
        //判断用户是否已经注册过
        Logininfo logininfo = logininfoMapper.selectByAccountAndType(weChatDto.getPhone(), 1);
        //注册过
        if (logininfo != null) {
            throw new CustomException("用户已注册！");
        }
        //没有注册过，保存注册信息
        String salt = StrUtils.getComplexRandomString(32);
        String md5Password = MD5Utils.encrypByMd5(weChatDto.getPassword() + salt);

        //设置默认值
        User user = dto2User(weChatDto, salt, md5Password);
        Logininfo newLogininfo = user2Logininfo(user);
        //先保存logininfo再保存user最后保存wxuser
        //1.保存logininfo
        logininfoMapper.insert(newLogininfo);
        //2、保存user
        user.setLogininfoId(newLogininfo.getId());
        userMapper.insert(user);
        //3、保存wxuser
        //获取用户基本信息
        String userUrl = WxConstants.GET_USER_URL;
        String url = userUrl.replace("ACCESS_TOKEN", weChatDto.getAccessToken())
                .replace("OPENID", weChatDto.getOpenId());
        String s = HttpClientUtils.httpGet(url);
        //将String转为Json格式
        JSONObject resultJson = JSONObject.parseObject(s);
        //设置Wxuser的默认信息
        Wxuser wxuser = json2Wxuser(resultJson);
        wxuser.setOpenid(weChatDto.getOpenId());
        wxuser.setUserId(user.getId());
        wxuserMapper.insert(wxuser);

        //注册完毕，免密登录
       /* String token = UUID.randomUUID().toString();
        //将用户信息保存到redis中
        redisTemplate.opsForValue().set(token, newLogininfo, 30, TimeUnit.MINUTES);

        //返回给前端的参数
        Map<String, Object> resultMap = new HashMap<>();
        newLogininfo.setSalt(null);
        newLogininfo.setPassword(null);
        resultMap.put("token", token);
        resultMap.put("logininfo", newLogininfo);
        return new AjaxResult().thisSuccess().setResultObj(resultMap);*/
        //用jwt替换上边无状态token
        return new AjaxResult().thisSuccess().setResultObj(loginSuccessHandler(logininfo));

    }

    //设置user的默认信息
    private User dto2User(WeChatDto weChatDto, String salt, String md5Password) {
        User user = new User();
        user.setUsername(weChatDto.getPhone());
        user.setPhone(weChatDto.getPhone());
        user.setSalt(salt);
        user.setPassword(md5Password);
        user.setState(1);
        user.setCreatetime(new Date());
        return user;
    }

    //设置logininfo的默认信息
    private Logininfo user2Logininfo(User user) {
        Logininfo logininfo = new Logininfo();
        BeanUtils.copyProperties(user, logininfo);
        logininfo.setDisable(1);
        logininfo.setType(1);
        return logininfo;
    }

    //设置Wxuser的默认信息
    private Wxuser json2Wxuser(JSONObject resultJson) {
        Wxuser wxuser = new Wxuser();
        wxuser.setNickname(resultJson.getString("nickname"));
        wxuser.setSex(resultJson.getInteger("sex"));
        wxuser.setAddress(resultJson.getString("city"));
        wxuser.setHeadimgurl(resultJson.getString("headimgurl"));
        wxuser.setUnionid(resultJson.getString("unionid"));
        return wxuser;
    }

}
